package xyz.lwm.lazycat.aop.support;

import lombok.Getter;
import org.jetbrains.annotations.Nullable;
import xyz.lwm.lazycat.aop.Invocation;
import xyz.lwm.lazycat.aop.InvocationHandler;

import java.lang.reflect.Method;

/**
 * implementation of invocation
 *
 * @author lwm
 */
class Call implements Invocation {

    /**
     * invoker for call original method
     */
    private final CheckedInvoker invoker;

    /**
     * original target
     */
    @Getter
    private final Object target;

    /**
     * original method
     */
    @Getter
    private final Method method;

    /**
     * original args
     */
    private final Ref<Object[]> argsRef;

    /**
     * return result
     */
    private final Ref<Object> resultRef;

    /**
     * whether returned
     */
    @Getter
    private boolean isReturn;

    Call(CheckedInvoker invoker, Object target, Method method,
         Ref<Object[]> argsRef, Ref<Object> resultRef) {
        this.invoker = invoker;
        this.target = target;
        this.method = method;
        this.argsRef = argsRef;
        this.resultRef = resultRef;
    }

    @Override
    public Object[] getArgs() {
        return argsRef.get();
    }

    @Override
    public void args(Object[] args) {
        argsRef.set(args);
    }

    @Override
    public Object getResult() {
        return resultRef.get();
    }

    @Override
    public void result(Object result) {
        resultRef.set(result);
        isReturn = true;
    }

    @Override
    public void invoke() throws Throwable {
        // set to result
        result(invoker.invoke());
    }

    /**
     * InvocationHandler 包装 <br>
     * 拦截 original invocation, 并得到 new invocation, 可以继续包装其他 handler, 以完成链式调用.
     *
     * @param handler invocation handler
     * @return new invocation
     */
    public Call wrap(InvocationHandler handler) {
        return new Call(() -> {
            // current invocation
            final Call call = this;
            // handle invocation
            handler.handle(call);
            // auto invoke if not executed
            if (!call.isReturn()) {
                call.invoke();
            }
            // return result
            return call.getResult();
        }, target, method, argsRef, resultRef);
    }

    /**
     * 值引用, 用于存储值
     *
     * @author lwm
     * @param <T>
     */
    static class Ref<T> {

        @Nullable
        private T value;

        public Ref() {
            this(null);
        }

        public Ref(@Nullable T value) {
            this.value = value;
        }

        public T get() {
            return value;
        }

        public void set(T value) {
            this.value = value;
        }

    }

    /**
     * @author lwm
     */
    @FunctionalInterface
    interface CheckedInvoker {

        /**
         * invoke and get result
         */
        Object invoke() throws Throwable;

    }

}
